﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Kotonoha.Server.Core.Utils;
using Kotonoha.Server.Dictionaries;
using Kotonoha.Server.Examples;
using Kotonoha.Server.Plugins;
using Kotonoha.Server.Settings;
using Kotonoha.Server.UI.Converters;
using Microsoft.Win32;
using Kotonoha.Server.Extensions;
using NHibernate;

namespace Kotonoha.Server.UI.Settings {
    /// <summary>
    /// Interaction logic for Dictionaries.xaml
    /// </summary>
    public partial class Dictionaries : Window {

        public const string WordDictName = "word-dic-name";
        public const string CharDicName = "char-dic-name";

        private string charDic;
        private string wordDic;
        private string exampleFile;
        private bool working;

        public Dictionaries() {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e) {
            PluginManager pm = PluginManager.Instance;
            this.DataContext = pm;
            Setting s = SettingsManager.Acquire(Setting.Common);
            object name = s[CharDicName];
            charBox.SelectedItem = name != null ? pm.FindCharPlugin((string) name) : null;
            name = s[WordDictName];
            wordBox.SelectedItem = name != null ? pm.FindWordPlugin((string)name) : null;
        }

        private void CharBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            Setting s = SettingsManager.Acquire(Setting.Common);
            s[CharDicName] = ((Plugin) charBox.SelectedItem).Name;
        }

        private void WordBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            Setting s = SettingsManager.Acquire(Setting.Common);
            s[WordDictName] = ((Plugin)wordBox.SelectedItem).Name;
        }

        private void SetDictionary_Click(object sender, RoutedEventArgs e) {
            Button b = (Button) sender;
            string s = (string) b.Tag;
            bool ok = false;
            if (Equals(s, "C")) {
                Plugin plugin = ((Plugin) charBox.SelectedItem);
                if (plugin != null) {
                    charDic = lookup(plugin.Mask);
                    ok = charDic != null;
                }
            } else {
                Plugin plugin = ((Plugin)wordBox.SelectedItem);
                if (plugin != null) {
                    wordDic = lookup(plugin.Mask);
                    ok = wordDic != null;
                }
            }

            if (ok) {
                b.Content = "Selected";
            } else {
                b.Content = "Select";
            }
        }

        private void SetExamles_Click(object sender, RoutedEventArgs e) {
            exampleFile = lookup("examples");
            Button b = (Button) sender;
            if (exampleFile != null) {
                b.Content = "Selected";
            } else {
                b.Content = "Select";
            }
        }

        private string lookup(string mask) {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = String.Format("{0}|{0}", mask);
            bool? dialog = ofd.ShowDialog(this);
            return (dialog ?? false) ? ofd.FileName : null;
        }

        private void appendConsole(string line) {
            Dispatcher dispatcher = document.Dispatcher;
            if (dispatcher.CheckAccess()) {
                appenConsoleInner(line);
            } else {
                dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<string>(appenConsoleInner), line);
            }
        }

        private void appenConsoleInner(string line) {
            TextPointer pointer = document.ContentStart;
            pointer.InsertTextInRun(line);
            pointer.InsertLineBreak();
        }

        private void LoadBtn_Click(object sender, RoutedEventArgs e) {
            if (charDic == null) {
                appendConsole("Char dictionary is not selected");
                return;
            }

            if (wordDic == null) {
                appendConsole("Word dictionary is not selected");
            }
            
            if (exampleFile == null) {
                appendConsole("Example file is not selected");
            }

            PluginsDataHolder holder = new PluginsDataHolder {
                                                                 charPlugin = (CharPlugin) charBox.SelectedItem,
                                                                 wordPlugin = (WordPlugin) wordBox.SelectedItem
                                                             };
            if (!working) {
                ThreadPool.QueueUserWorkItem(doWork, holder);
            } else {
                appendConsole("Importing in progress... please wait");
            }
        }

        private struct PluginsDataHolder {
            internal CharPlugin charPlugin;
            internal WordPlugin wordPlugin;
        }

        private void doWork(object dummy) {
            lock (this) {
                working = true;
            }

            Stopwatch w = new Stopwatch();
            w.Start();

            appendConsole("Deleting indices");
            DropIndices();

            PluginsDataHolder holder = (PluginsDataHolder) dummy;
            saveCharDic(holder.charPlugin);
            saveWords(holder.wordPlugin);

            appendConsole("Building Indices");
            buildIndices();

            w.Stop();

            appendConsole(String.Format("Import complete in {0}", w.Elapsed));

            Setting s = SettingsManager.Acquire(Setting.Common);
            s["dic-version"] = DictionaryStatusProvider.CurrentVersion;

            lock (this) {
                working = false;
            }
        }

        private void DropIndices() {
            using (var s = HibernateUtil.Dictionary.createSession()) {
                for (int i = 0; i < IdxNames.Length; i++) {
                    s.CreateSQLQuery(String.Format("drop index if exists {0};", IdxNames[i])).ExecuteUpdate();
                }
            }
            
        }

        private readonly string[] IdxNames = new[] {
            "IDX_DIC_WORD_READING",
            "IDX_DIC_WORD_WRITING",
            "IDX_DIC_WORD_MEANING",
            "IDX_DIC_CHAR_WRITING",
            "IDX_DIC_WORD_READING_ID",
            "IDX_DIC_WORD_WRITING_ID",
            "IDX_DIC_EXAM_INFO_WORD",
            "IDX_DIC_EXAM_INFO_EXAM"
    };

        private readonly string[] IdxData = new[] {
            "DIC_WORD_READINGS(READING)",
            "DIC_WORD_WRITINGS(WRITING)",
            "DIC_WORD_MEANINGS_DATA(MEANING)",
            "DIC_CHAR(WRITING)",
            "DIC_WORD_READINGS(WORD_ID)",
            "DIC_WORD_WRITINGS(WORD_ID)",
            "DIC_EXAMPLE_WORD(WORD_ID)",
            "DIC_EXAMPLE_WORD(EXAMPLE_ID)"
    };

        private void buildIndices() {
            using (var s = HibernateUtil.Dictionary.createSession()) {
                for (int i = 0; i < IdxNames.Length; i++) {
                    s.CreateSQLQuery(String.Format("create index {0} on {1};", IdxNames[i], IdxData[i])).ExecuteUpdate();
                }
            }
        }

        private void saveWords(WordPlugin plugin) {
            appendConsole("Reading word dictionary from file");
            var dictionary = plugin.read(x => wordDic);
            using (var s = HibernateUtil.Dictionary.createSession()) {
                ITransaction transaction = s.BeginTransaction();
                try {
                    double size = dictionary.Count;
                    using (var cb = new DisposableCallback("Word dictionary import", 8, appendConsole)) {
                        dictionary.ForEach((k, i) => { s.Save(k); cb.Progress(k, i / size); });
                    }
                    transaction.Commit();
                    appendConsole("Word dictionary import complete.");
                } catch {
                    transaction.Rollback();
                    throw;
                }
            }
            using (var ses = HibernateUtil.Dictionary.createStatelessSession()) {
                ITransaction tran = ses.BeginTransaction();
                try {
                    SaveExamles(exampleFile, dictionary, ses);
                    tran.Commit();
                } catch {
                    tran.Rollback();
                    throw;
                }
            }
        }

        private void saveCharDic(CharPlugin plugin) {
            appendConsole("Loading character dictionary from file");
            ICharDictionary dictionary = plugin.read(x => charDic);
            using (var s = HibernateUtil.Dictionary.createSession()) {
                ITransaction transaction = s.BeginTransaction();
                try {
                    double size = dictionary.Count;
                    using (var cb = new DisposableCallback("Character dictionary import", 4, appendConsole)) {
                        dictionary.ForEach((k, i) => { s.Save(k); cb.Progress(k, i / size); });
                    }
                    transaction.Commit();
                    appendConsole("Character dictionary import complete.");
                } catch {
                    transaction.Rollback();
                    throw;
                }
            }
        }

        private void SaveExamles(string filename, IWordDictionary dic, IStatelessSession session) {
            using (FileStream stream = File.OpenRead(filename)) {
                using (var cb = new DisposableCallback("Import examples", 15, appendConsole)) {
                    foreach (var i in ExampleFileReader.read(stream, Encoding.GetEncoding("euc-jp"), cb.Progress)) {
                        ExampleEntry example = Examples.Examples.createExample(i.Example);
                        session.Insert(example);

                        HashSet<WordEntry> done = new HashSet<WordEntry>();
                        foreach (Word w in i.Positions) {
                            IEnumerable<WordEntry> enumerable = dic.ByWriting(w.Content);
                            foreach (WordEntry entry in enumerable) {
                                if (done.Contains(entry)) {
                                    continue;
                                }
                                done.Add(entry);
                                var data = new ExampleInfo();
                                data.Example = example;
                                data.Number = w.Meaning;
                                data.Recommended = w.Recommended ? 1 : 0;
                                data.Word = entry;
                                session.Insert(data);
                            }
                        }
                    }
                }
            }
        }

        private class MessageCallback {
            private string name;
            private int times = 1;
            private readonly Action<string> PrintFnc;
            private double step;
            private Stopwatch watch = new Stopwatch();

            public MessageCallback(string name, int times, Action<string> printFnc) {
                this.name = name;
                PrintFnc = printFnc;
                step = 1.0/times;
            }

            public void Start() {
                PrintFnc(String.Format("{0}: Started", name));
                watch.Start();
            }

            public void Stop() {
                PrintFnc(String.Format("{0}: Finished", name));
                watch.Stop();
            }

            public void Progress(object sender, double val) {
                if (val > times * step) {
                    double speed = val/watch.Elapsed.Ticks;
                    TimeSpan t = new TimeSpan((long) ((1-val)/speed));
                    PrintFnc(String.Format("{0}: {1:F1}%, estimated {2}", name, val*100, t));
                    ++times;
                }
            }
        }

        private class DisposableCallback : MessageCallback, IDisposable {
            public DisposableCallback(string name, int times, Action<string> printFnc) : base(name, times, printFnc) {
                Start();
            }

            public void Dispose() {
                Stop();
            }
        }
    }
}
